// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetaryrenderable.h"

#include <Ogre.h>
#include <OgreHardwareBufferManager.h>
#include "planetaryscenemanager.h"
#include "planetaryheightmapdata.h"
#include "planetaryoptions.h"
#include "planetarycommon.h"
#include "planetaryindexbuffermanager.h"
#include "btBulletCollisionCommon.h"

namespace Ogre
{
	String PlanetaryRenderable::mType = "PlanetaryRenderable";

	PlanetaryRenderable::PlanetaryRenderable(PlanetarySceneManager *mgr) : Renderable(), MovableObject()
	{
		mSceneMgr = mgr;
		mOptions = mgr->getOptions();

		mScale = mOptions->mPlanetRadius;

		mVertices = 0;
		mIndices = 0;

		mLODMorphFactor = 0.0;

		// FIXME: Read this from init aswell
		mPlanet = 0;

		mLightListDirty = true;
		mIsLoaded = false;
		mIsInit = false;

		mDebugColours = false;
		
		mIndexVertexArrays = NULL;
		mGroundShape = NULL;
		mCollisionVertices = NULL;
		mCollisionIndices = NULL;
		mBody = NULL;
		mBodyIsInWorld = false;
	}

	PlanetaryRenderable::~PlanetaryRenderable()
	{
		if(mIsLoaded)
			unload();

		if(mIsInit)
			uninit();
	}

	void PlanetaryRenderable::_notifyCurrentCamera(Camera* cam)
	{
		if(!mIsLoaded || !mIsInit)
		{
			mVisible = false;
			return;
		}

		mVisible = true;
	}

	void PlanetaryRenderable::_updateRenderQueue(RenderQueue* queue)
	{
		queue->addRenderable(this);
	}

	void PlanetaryRenderable::_notifyAttached(Node* parent, bool isTagPoint)
	{
		MovableObject::_notifyAttached(parent, isTagPoint);
	}
	
	void PlanetaryRenderable::setVisible(bool visible)
	{
		if(visible)
		{
			if(mBody && !mBodyIsInWorld)
			{
				mSceneMgr->getDynamicsWorld()->addRigidBody(mBody);
				mBodyIsInWorld = true;
			}
		}
		
		else
		{
			if(mBodyIsInWorld)
			{
				mSceneMgr->getDynamicsWorld()->removeRigidBody(mBody);
				mBodyIsInWorld = false;
			}
		}
		
		MovableObject::setVisible(visible);
	}

	void PlanetaryRenderable::getRenderOperation(RenderOperation& op)
	{
		op.useIndexes = true;
		op.operationType = RenderOperation::OT_TRIANGLE_LIST;
		op.vertexData = mVertices;
		op.indexData = mIndices;
	}

	void PlanetaryRenderable::init(unsigned int zone, unsigned int pageX, unsigned int pageZ, unsigned int tileX, unsigned int tileZ, PlanetaryTile *parent)
	{
		assert(!mIsInit);

		mZone	= zone;
		mPageX	= pageX;
		mPageZ	= pageZ;
		mTileX	= tileX;
		mTileZ	= tileZ;

		mParent = parent;


		// Turn on debuging of tiles
		mDebugColours = mOptions->mTileDebugSquares;

		mCastShadows = false;

		String matname = mOptions->mPlanetMaterial + "/Zone_" + StringConverter::toString(mZone+1,2,'0');
		//mMaterial = MaterialManager::getSingleton().getByName(matname);
		mMaterial = MaterialManager::getSingleton().getByName(mOptions->mPlanetMaterial + "_groundFromSpace");

		// Default query flags to top bit so users can exclude it if they wish
		MovableObject::mQueryFlags = SceneManager::WORLD_GEOMETRY_TYPE_MASK;
		MovableObject::setRenderQueueGroup(mSceneMgr->getWorldGeometryRenderQueue());

		mVertices = new VertexData();
		mVertices->vertexStart = 0;
		mVertices->vertexCount = mOptions->TileSize * mOptions->TileSize;

		VertexDeclaration *decl = mVertices->vertexDeclaration;

		size_t offset = 0;
		decl->addElement(0,0,VET_FLOAT3,VES_POSITION);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT3, VES_NORMAL);
		offset += VertexElement::getTypeSize(VET_FLOAT3);
		decl->addElement(0,offset,VET_FLOAT2, VES_TEXTURE_COORDINATES);
		offset += VertexElement::getTypeSize(VET_FLOAT2);
		decl->addElement (0, offset, VET_COLOUR, VES_DIFFUSE);
		offset += VertexElement::getTypeSize (VET_COLOUR);

		mIndices = new IndexData();
		mIndices->indexStart = 0;
		mIndices->indexCount = mSceneMgr->getIndexBufferManager()->getIndexBufferSize(INDEX_BUFFER_TILE);

		// Create delta buffer for all except the lowest mipmap
		const unsigned int maxmip = mOptions->mNumerOfTilesLODs - 1;
		mDeltaBuffers = new HardwareVertexBufferSharedPtr[maxmip];

		// Create additional element for delta
		decl->addElement(1, 0, VET_SHORT2, VES_BLEND_WEIGHTS);

		mCustomGpuParameters = Vector4(1.0,1.0,1.0,mLODMorphFactor);

		mIsInit = true;
	}

	void PlanetaryRenderable::load()
	{
		assert(!mIsLoaded);
		assert(mIsInit);
		assert(mVertices);

		mBounds.setNull();

		VertexDeclaration *decl = mVertices->vertexDeclaration;
		VertexBufferBinding *bind = mVertices->vertexBufferBinding;

		// Setup vertices
		HardwareVertexBufferSharedPtr vbuf = 
			HardwareBufferManager::getSingleton().createVertexBuffer(
				decl->getVertexSize(0),
				mVertices->vertexCount,
				HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		bind->setBinding(0,vbuf);


		for (unsigned int k = 0; k < mOptions->mNumerOfTilesLODs - 1; k++)
		{
			mDeltaBuffers[k]  = createDeltaBuffer(); 
		}

		const VertexElement* poselem = decl->findElementBySemantic(VES_POSITION);

		unsigned int vertexSize = vbuf->getVertexSize();

		uchar *pBuffer = (uchar*)vbuf->lock(HardwareBuffer::HBL_DISCARD);

		// Find out vertex orientation by zone
		switch(mZone)
		{
		case ZONE_UP:
			loadUp(poselem,pBuffer,vertexSize);
			break;
		case ZONE_DOWN:
			loadDown(poselem,pBuffer,vertexSize);
			break;
		case ZONE_FRONT:
			loadFront(poselem,pBuffer,vertexSize);
			break;
		case ZONE_BACK:
			loadBack(poselem,pBuffer,vertexSize);
			break;
		case ZONE_LEFT:
			loadLeft(poselem,pBuffer,vertexSize);
			break;
		case ZONE_RIGHT:
			loadRight(poselem,pBuffer,vertexSize);
			break;
		}

		vbuf->unlock();

		// Get indices
		mIndices->indexBuffer = mSceneMgr->getIndexBufferManager()->getIndexBuffer(INDEX_BUFFER_TILE);
		
		LogManager::getSingleton().logMessage("Starting physics loading");
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			// make a system-memory copy of the indices, lock sounds very exclusive to me, and I need this data for while, so I'm not using shadow buffers
			// however, it is fucking stupid to keep a local copy of this in each and every tile, considering that it's the same data for each of them.
			// this should be moved into PlanetaryIndexBufferManager or something.
			unsigned int indexCopySize = 2 * 3 * (mOptions->TileSize - 1) * (mOptions->TileSize - 1);
			mCollisionIndices = mSceneMgr->getIndexBufferManager()->getIndexShadowBuffer(INDEX_BUFFER_TILE);

			LogManager::getSingleton().logMessage("Picked up indices");
			/*int index = 0;
			for(unsigned int i = 0; i < (mOptions->TileSize - 1); i++)
			{
				for(unsigned int j = 0; j < (mOptions->TileSize - 1); j++)
				{
					// Face 1 of the quad
					mCollisionIndices[index++] = i * (mOptions->TileSize) + j;
					mCollisionIndices[index++] = (i+1) * (mOptions->TileSize) + j;
					mCollisionIndices[index++] = i * (mOptions->TileSize) + (j + 1);
	
					// Face 2 of the quad
					mCollisionIndices[index++] = i * (mOptions->TileSize) + (j + 1);
					mCollisionIndices[index++] = (i+1) * (mOptions->TileSize) + j;
					mCollisionIndices[index++] = (i+1) * (mOptions->TileSize) + (j+1);
				}
			}*/
			
			// create interface
			LogManager::getSingleton().logMessage("Creating TriangleIndexVertexArray");
			unsigned int totalTriangles = 2 * (mOptions->TileSize - 1) * (mOptions->TileSize - 1);
			unsigned int totalVertices = (mOptions->TileSize) * (mOptions->TileSize);
			unsigned int vertexStride = sizeof(btVector3);
			unsigned int indexStride = 3*sizeof(int);
			mIndexVertexArrays = new btTriangleIndexVertexArray(totalTriangles, mCollisionIndices, indexStride, totalVertices, (btScalar*) &mCollisionVertices[0].x(), vertexStride);
			
			// create collision shape
			bool useQuantizedAabbCompression = true;
			
			LogManager::getSingleton().logMessage("Creating collision shape");
			// the collision shape can be serialized to disk if necessary.
			mGroundShape = new btBvhTriangleMeshShape(mIndexVertexArrays,useQuantizedAabbCompression);
			
			// create rigidbody
			LogManager::getSingleton().logMessage("Creating motion state");
			const float mass = 0.0f;
			btTransform startTransform;
			startTransform.setIdentity();
			startTransform.setOrigin(btVector3(0,0,0));
			btVector3 localInertia(0,0,0);
			
			btDefaultMotionState* myMotionState = new btDefaultMotionState(startTransform);
	
			LogManager::getSingleton().logMessage("Creating construction info");
			btRigidBody::btRigidBodyConstructionInfo cInfo(mass,myMotionState,mGroundShape,localInertia);
	
			LogManager::getSingleton().logMessage("Creating rigid body");
			mBody = new btRigidBody(cInfo);
			mBody->setCollisionFlags(mBody->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT);
		}
		LogManager::getSingleton().logMessage("Done!");

		// set bounds
		float x = (mBounds.getMaximum().x - mBounds.getMinimum().x);
		float y = (mBounds.getMaximum().y - mBounds.getMinimum().y);
		float z = (mBounds.getMaximum().y - mBounds.getMinimum().y);

		mCenter = Vector3(mBounds.getMinimum().x + (x/2.0f),mBounds.getMinimum().y + (y/2.0f),mBounds.getMinimum().z + (z/2.0f));

		mBoundingRadius = Math::Sqrt(Math::Sqr(x) + Math::Sqr(y) + Math::Sqr(z)) / 2.0f;
		
		setMaterial(mMaterial);

		mIsLoaded = true;
	}

	void PlanetaryRenderable::loadUp(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
                // Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}
		
		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;

		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;

		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{
				float x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) - 1.0f;

				float y = 1.0f;

				float z = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) - 1.0f;

				const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z

				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down, norm;

				if(j == 0)
				{
					float tmpx = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j+1)) /
						zoneSize)) - 1.0f;
					//nonormal = true;
					// Copy the normal of the next vertex, as we don't have a vertex left of this one
					pos.x = tmpx;
					pos.y = y;
					pos.z = z;

					hscale = 1.0f + (((fh[j+1] - 32768) / 65536) * mOptions->mTerrainScale);

					pos = cubeToSphereCoords(pos.x,pos.y,pos.z) * mScale * hscale;

					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
						zoneSize)) - 1.0f;
					left.y = y;
					left.z = z;
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;

					if(i == mOptions->TileSize -1)
					{
						down.x = tmpx;
						down.y = y;
						down.z = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
							zoneSize)) - 1.0f;

						fh = heightmap->getTileDataRow(mTileX,mTileZ,i);
						hscale = 1.0f + (((fh[j+1] - 32768) / 65536) * mOptions->mTerrainScale);
						down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
					}
					else
					{
						down.x = tmpx;
						down.y = y;
						down.z = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
							zoneSize)) - 1.0f;

						fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
						hscale = 1.0f + (((fh[j+1] - 32768) / 65536) * mOptions->mTerrainScale);
						down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
					}

				}
				else if(i == mOptions->TileSize - 1)
				{
					//nonormal = true;

					fh = heightmap->getTileDataRow(mTileX,mTileZ,i-1);

					float tmpz = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i-1)) /
						zoneSize)) - 1.0f;
					//nonormal = true;
					// Copy the normal of the next vertex, as we don't have a vertex left of this one
					pos.x = x;
					pos.y = y;
					pos.z = tmpz;

					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

					pos = cubeToSphereCoords(pos.x,pos.y,pos.z) * mScale * hscale;

					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;

					left.y = y;
					left.z = tmpz;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;

					down.x = x;
					down.y = y;
					down.z = z;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				else
				{
					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;
					left.y = y;
					left.z = z;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;

					down.x = x;
					down.y = y;
					down.z = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) - 1.0f;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				if(nonormal)
				{
					norm = pos;
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				Vector2 tex(((x + 1.0f) / 2), ((z + 1.0f) / 2));
				*pPos++ = tex.x;
				*pPos++ = tex.y;

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::loadDown(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		// Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}

		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;

		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;
		
		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{
				float x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) - 1.0f;

				float y = -1.0f;

				float z = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) + 1.0f;

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z
				
				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down;

				if(j == 0)
				{
					nonormal = true;
				}
				else
				{
					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;
					left.y = y;
					left.z = z;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;
				}


				if(i == mOptions->TileSize - 1)
				{
					nonormal = true;
				}
				else
				{
					down.x = x;
					down.y = y;
					down.z = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) + 1.0f;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				Vector3 norm;
				
				if(nonormal)
				{
					norm = pos;	
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				
				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((z + 1.0f) / 2);

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::loadFront(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		 // Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}

		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;

		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;
		
		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{
				float x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) - 1.0f;

				float y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) + 1.0f;

				float z = 1.0f;

				const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z
				
				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down;

				if(j == 0)
				{
					nonormal = true;
				}
				else
				{
					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;
					left.y = y;
					left.z = z;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;
				}


				if(i == mOptions->TileSize - 1)
				{
					nonormal = true;
				}
				else
				{
					down.x = x;
					down.y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) + 1.0f;
					down.z = z;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				Vector3 norm;
				
				if(nonormal)
				{
					norm = pos;	
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				
				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::loadBack(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		// Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}

		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;

		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;
		
		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{
				float x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) - 1.0f;

				float y = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) - 1.0f;

				float z = -1.0f;

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z
				
				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down;

				if(j == 0)
				{
					nonormal = true;
				}
				else
				{
					left.x = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;
					left.y = y;
					left.z = z;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;
				}


				if(i == mOptions->TileSize - 1)
				{
					nonormal = true;
				}
				else
				{
					down.x = x;
					down.y = ( 2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) - 1.0f;
					down.z = z;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				Vector3 norm;
				
				if(nonormal)
				{
					norm = pos;	
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				
				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((x + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::loadLeft(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		// Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}
		
		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;
		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;
		
		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{

				float x = -1.0f;

				float y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) + 1.0f;

				float z = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) - 1.0f;

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z
				
				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down;

				if(j == 0)
				{
					nonormal = true;
				}
				else
				{
					left.x = x;
					left.y = y;
					left.z = (2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) - 1.0f;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;
				}


				if(i == mOptions->TileSize - 1)
				{
					nonormal = true;
				}
				else
				{
					down.x = x;
					down.y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) + 1.0f;
					down.z = z;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				Vector3 norm;
				
				if(nonormal)
				{
					norm = pos;	
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				
				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((z + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::loadRight(const VertexElement* poselem, uchar *pBuffer,unsigned int vertexSize)
	{
		// Bullet stuff
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			mCollisionVertices = new btVector3[mOptions->TileSize*mOptions->TileSize];
		}

		const PlanetaryHeightmapData *hdata = mSceneMgr->getHeightmapData();

		const PlanetaryPageHeightmap *heightmap = hdata->getHeightmap(mPlanet,mZone,mPageX,mPageZ);

		float *pPos;

		const float zoneSize = static_cast<float>(mOptions->ZoneSize * mOptions->PageSize * (mOptions->TileSize - 1));
		
		int index = 0;
		
		for(unsigned int i = 0; i < mOptions->TileSize; i++)
		{
			const float *fh = heightmap->getTileDataRow(mTileX,mTileZ,i);

			for(unsigned int j = 0; j < mOptions->TileSize; j++)
			{
				float x = 1.0f;

				float y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + i) /
					zoneSize)) + 1.0f;

				float z = (-2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + j) /
					zoneSize)) + 1.0f;

				float hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);

				Vector3 pos = cubeToSphereCoords(x,y,z) * mScale * hscale;

				poselem->baseVertexPointerToElement(pBuffer, &pPos);

				mBounds.merge(pos);

				// Vertex coords
				*pPos++ = pos.x; // X
				*pPos++ = pos.y; // Y
				*pPos++ = pos.z; // Z
				
				if(mSceneMgr->isBulletPhysicsEnabled())
				{
					// make a copy of the vertex positions in system memory for use in physics-engine
					mCollisionVertices[index++] = btVector3(pos.x,pos.y,pos.z);
				}

				// Normals
				bool nonormal = false;
				Vector3 left, down;

				if(j == 0)
				{
					nonormal = true;
				}
				else
				{
					left.x = x;
					left.y = y;
					left.z = (-2.0f * (static_cast<float>((mPageX * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileX * (mOptions->TileSize - 1)) + (j-1)) /
						zoneSize)) + 1.0f;
					hscale = 1.0f + (((fh[j-1] - 32768) / 65536) * mOptions->mTerrainScale);
					left = cubeToSphereCoords(left.x,left.y,left.z) * mScale * hscale;
				}


				if(i == mOptions->TileSize - 1)
				{
					nonormal = true;
				}
				else
				{
					down.x = x;
					down.y = (-2.0f * (static_cast<float>((mPageZ * mOptions->PageSize * (mOptions->TileSize - 1)) + (mTileZ * (mOptions->TileSize - 1)) + (i+1)) /
						zoneSize)) + 1.0f;
					down.z = z;
					fh = heightmap->getTileDataRow(mTileX,mTileZ,i + 1);
					hscale = 1.0f + (((fh[j] - 32768) / 65536) * mOptions->mTerrainScale);
					down = cubeToSphereCoords(down.x,down.y,down.z) * mScale * hscale;
				}
				
				Vector3 norm;
				
				if(nonormal)
				{
					norm = pos;	
				}
				else
				{
					left = left - pos;
	
					down = down - pos;
	
					left.normalise();
					down.normalise();
					
					norm = left.crossProduct( down );
				}

				
				norm.normalise();
				*pPos++ = norm.x; // X
				*pPos++ = norm.y; // Y
				*pPos++ = norm.z; // Z

				// Texture Coords
				*pPos++ = ((z + 1.0f) / 2);
				*pPos++ = ((y + 1.0f) / 2);

				// Vertex colours
				ColourValue RGBA_precalc;

				if(mDebugColours)
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.b = (mTileX % 2 ? (mTileZ % 2 ? 1.0f : 0.0f) : (mTileZ % 2 ? 0.0f : 1.0f));
					RGBA_precalc.a = 1.0f;
				}
				else
				{
					RGBA_precalc.r = 1.0f;
					RGBA_precalc.g = 1.0f;
					RGBA_precalc.b = 1.0f;
					RGBA_precalc.a = 1.0f;
				}

				RGBA *pColour = (RGBA*)pPos;

				Root::getSingleton().convertColourValue (RGBA_precalc,pColour);

				pBuffer += vertexSize;
			}
		}
	}

	void PlanetaryRenderable::uninit()
	{
		assert(mIsInit);

		delete[] mDeltaBuffers;
		mDeltaBuffers = NULL;

		mVertices->vertexDeclaration->removeAllElements();
		delete mVertices;
		mVertices = NULL;

		delete mIndices;
		mIndices = NULL;

		mZone = 0;
		mPageX = 0;
		mPageZ = 0;
		mTileX = 0;
		mTileZ = 0;


		mIsInit = false;
	}

	void PlanetaryRenderable::unload()
	{
		assert(mIsLoaded);

		mVertices->vertexBufferBinding->unsetAllBindings();
		mIndices->indexBuffer.setNull();

		for (unsigned int k = 0; k < mOptions->mNumerOfTilesLODs - 1; k++)
		{
			mDeltaBuffers[k].setNull();
		}
		
		if(mSceneMgr->isBulletPhysicsEnabled())
		{
			delete mIndexVertexArrays;
			delete mGroundShape;
			
			//delete[] mCollisionIndices; // Stores in index buffer manager
			delete[] mCollisionVertices;
			
			if(mBody && mBody->getMotionState())
				delete mBody->getMotionState();
			delete mBody;
		}
		
		mIsLoaded = false;
	}
	
	void PlanetaryRenderable::setMaterial(MaterialPtr mat)
	{
		mMaterial = mat;

		GpuProgramParametersSharedPtr pParams;
		Ogre::Pass *pPass;
		
		pPass = mMaterial->getTechnique(0)->getPass(0);
		pParams = pPass->getVertexProgramParameters();
		
		pParams->setNamedConstant("vPlanetCentre", Vector3(0,0,0));
		pParams->setNamedConstant("fMaxAltitude", 200.0f);
	}

	void PlanetaryRenderable::getWorldTransforms(Matrix4 *xform) const
	{
		*xform = mParentNode->_getFullTransform();
	}

	const Quaternion &PlanetaryRenderable::getWorldOrientation() const
	{
		return mParentNode->_getDerivedOrientation();
	}

	const Vector3 &PlanetaryRenderable::getWorldPosition() const
	{
		return mCenter;
	}
	
	Real PlanetaryRenderable::getSquaredViewDepth(const Camera *cam) const
	{
		return (PlanetaryRenderable::getWorldPosition() - cam->getDerivedPosition()).squaredLength();
	}

	const LightList &PlanetaryRenderable::getLights() const
	{
		if (mLightListDirty)
		{
			getParentSceneNode()->getCreator()->_populateLightList(
					mCenter, 
					this->getBoundingRadius(), 
					mLightList);
					mLightListDirty = false;
		}
		return mLightList;
	}

	HardwareVertexBufferSharedPtr PlanetaryRenderable::createDeltaBuffer(void) const
	{
		// Delta buffer is a 1D Real buffer of height offsets
		HardwareVertexBufferSharedPtr buf = HardwareBufferManager::getSingleton().createVertexBuffer(
				VertexElement::getTypeSize(VET_SHORT2), 
				mOptions->TileSize * mOptions->TileSize,
				HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		return buf;

	}
}
